from math import pi, sin, cos
from time import time
import sys
import ctypes

import pymunk
import pyglet
from pyglet.gl import *
from pyglet import clock

import squirtle
from data import get_path, load_sound
from constants import *

glMatrix = ctypes.c_float * 16
FRICTION = 2

TYPE_BALL = 1
TYPE_TRIGGER = 2

GROUP_STATIC = 1

class Ball (object):
    max_avelocity = 15.
    torque = 7.
    max_time = 2.
    recharge_rate = 2/60.

    def __init__(self, position=(0, 0), init_score = 0, max_size = 2, size = 1):
        self.max_size = max_size
        self.size = size
        self.base_radius = 1.
        self.new_radius = self.get_size()
        self.last_size = size
        self.roll_direction = 0
        self.score = init_score
        self.is_resizing = False
        self.is_resting = True

        self.body = pymunk.Body(.2, .2)
        self.body.position = position

        self.shape = pymunk.Circle(self.body, self.get_size())
        self.shape.friction = FRICTION
        self.shape.elasticity = .2
        self.shape.collision_type = TYPE_BALL

        self.shape.entity = self
        self.power = 9
        self.timer = self.max_time
        self.power_enabled = True

        self._others = set()
        self._contacts = []
        self._distances = []


        self.s_hit = load_sound('h1.wav')
        self.s_drop = load_sound('d1.wav')

    def drop(self):
        self.s_drop.play()

    def reset(self):
        self.size = 1
        self.new_radius = self.get_size()
        self.roll_direction = 0

    def get_size(self):
        return 3 ** self.size * self.base_radius

    def shift_up(self):
        if self.power_enabled and self.size < self.max_size:
            self._shift(1)

    def shift_down(self):
        if self.power_enabled and self.size > 0:
            self._shift(-1)

    def _shift(self, step):
        if self.is_resting:
            if not self.power:
                return
            self.power -= 1
        self.size += step
        self.new_radius = self.get_size()

    def roll_left(self):
        self.roll_direction = 1

    def roll_right(self):
        self.roll_direction = -1

    step = .1

    def think(self, level_done):
        if level_done:
            brake = -self.body.velocity * .05
            self.body.apply_impulse(brake)
            return

        self.is_resizing = self.shape.radius != self.new_radius
        self.is_resting = self.size == 1

        change = self.roll_direction * self.body.angular_velocity
        base_torque = self.torque * 2**self.size * self.roll_direction
        if change >= 0.:
            self.body.torque = max(0., self.max_avelocity - abs(self.body.angular_velocity)) / self.max_avelocity * base_torque
        else:
            self.body.torque = base_torque

        if self.is_resizing:
            modifier = self.size + 1 if self.shape.radius > self.new_radius else self.size
            self.cur_step = step = self.step * modifier * self.base_radius
            delta = self.new_radius - self.shape.radius
            delta = max(-step, min(delta, step))
            self.shape.unsafe_set_radius(self.shape.radius + delta)
            self.delta = delta

            if abs(delta) < step:
                self.new_radius = None

        if self.size == 1 and self.timer < self.max_time:
            self.timer += min(self.recharge_rate, self.max_time - self.timer)
        if self.size == 1 and self.timer == self.max_time:
            self.power_enabled = True
        if self.size != 1:
            if self.timer > 0:
                self.timer -= 1/60.
            else:
                self.power_enabled = False

        self._others = set()
        self._distances = []
        self._contacts = []

    def collide(self, space, arbiter):
        for s in arbiter.shapes:
            self._others.add(id(s))
        for c in arbiter.contacts:
            self._contacts.append(c)
            self._distances.append(c.distance)

    def post_step(self):
        do_offset = bool(self._contacts and self.is_resizing)

        if self.is_resizing:
            limit = -self.cur_step * 1.1 * (max(self.size, self.last_size) + 1)
            if len(self._others) > 2 and min(self._distances) < limit:
                if self.is_resizing and self.delta > 0:
                    self.shape.unsafe_set_radius(self.shape.radius - self.delta)
                    self.body.apply_impulse(-self.body.velocity * .1)
                    do_offset = False

        if do_offset:
            for c in self._contacts:
                self.body.position += c.normal * c.distance

    def render(self):
        glPushMatrix()

        p = self.body.position

        glTranslatef(p.x, p.y, 0)


        r = self.body.rotation_vector
        s = self.shape.radius

        glScalef(s, s, s)

        glPushMatrix()

        matrix = glMatrix(
            r.x, r.y, 0, 0,
            -r.y, r.x, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1)

        glMultMatrixf(matrix)

        glBegin(GL_LINE_LOOP)
        glColor3f(0, 0,  0)
        glVertex2f(0, -1)
        for i in range(11):
            a = 2*i*pi/10
            x, y = sin(a), cos(a)
            glVertex2f(x, y)
        glEnd()

        glPopMatrix()

        if self.size != 1 or self.timer < self.max_time:
            bar = 4 * self.timer / self.max_time - 2
            glBegin(GL_LINES)
            if self.power_enabled:
                glColor3f(0, .5, 0)
            else:
                glColor3f(.5, 0, 0)
            glVertex2f(-2, 2)
            glVertex2f(bar, 2)
            glEnd()

        glPopMatrix()

class Trigger (object):
    def __init__(self, level, path):
        self.level = level
        self.level = level
        self.done = False
        self.pos = path.get_pos()
        s = self.size
        points = [(-s, -s), (s, -s), (s, s), (-s, s)]
        self.init(points)
        self.body.position = self.pos

    def init(self, points):
        self.body = pymunk.Body(pymunk.inf, pymunk.inf)
        self.shape = pymunk.Poly(self.body, points)
        self.shape.sensor = 1
        self.shape.group = GROUP_STATIC
        self.shape.collision_type = TYPE_TRIGGER
        self.shape.entity = self

    @staticmethod
    def cb_begin_collide(space, arbiter, ball):
        for shape in arbiter.shapes:
            if isinstance(shape.entity, Trigger):
                shape.entity.collide_begin(space, arbiter, ball)
        return True

    def draw_star(self):
        size = self.size * 1.5
        glBegin(GL_LINE_LOOP)
        for i in range(10):
            a = 2*pi*i/10
            r = (i % 2) * size / 2 + size / 2
            x = sin(a) * r
            y = cos(a) * r
            glVertex2f(x, -y)
        glEnd()


class Finish (Trigger):
    size = 15

    def __init__(self, level, path, par):
        super(Finish, self).__init__(level, path)
        self.par = int(par)
        self.sound = load_sound('done.wav')
        self.score = load_sound('score.wav')

    def collide_begin(self, space, arbiter, ball):
        self.level.completed = True
        self.level.before_par = max(0, self.par - (time() - self.level.started))
        self.done = True
        self.sound.play()
        return 0

    def render(self):
        if self.done:
            if self.size > 0:
                self.size -= .5
            else:
                return 0

        glPushMatrix()
        p = self.body.position
        glTranslatef(p.x, p.y, 0)
        glRotatef(self.level.frame / 5., 0, 0, 1)
        
        glColor3f(0, 0, 0)
        self.draw_star()
        glPopMatrix()

class Coin (Trigger):
    size = 2
    def __init__(self, level, path):
        super(Coin, self).__init__(level, path)
        self.sound = load_sound('coin.wav')

    @classmethod
    def compile(cls):
        cls.gl_list = glGenLists(1)
        print cls.gl_list

        glNewList(cls.gl_list, GL_COMPILE)

        glPushAttrib(GL_LINE_BIT)
        glLineWidth(2)
        glBegin(GL_LINE_LOOP)
        glColor3f(0, 0, 0)
        glVertex2f(0, cls.size)
        glVertex2f(-cls.size, -cls.size)
        glVertex2f(cls.size, -cls.size)
        glEnd()
        glPopAttrib()
        glEndList()

    def collide_begin(self, space, arbiter, ball):
        ball.score += 9
        space.remove(self.shape)
        self.done = True
        self.sound.play()

    def render(self):
        if self.done:
            return

        glPushMatrix()
        p = self.body.position
        glTranslatef(p.x, p.y, 0)
        glCallList(self.gl_list)
        glPopMatrix()

class Grow (Trigger):
    size = 5

    def __init__(self, level, path):
        print self
        self.level = level
        self.done = False
        self.pos = path.get_pos()
        s = self.size
        points = [(-s, -s), (s, -s), (s, s), (-s, s)]
        self.init(points)
        self.body.position = self.pos
        self.sound = load_sound('grow.wav')
        self.score = load_sound('score.wav')

    def collide_begin(self, space, arbiter, ball):
        if self.done:
            return
        
        ball.score += ball.power * POWER_SCORE


        ball.base_radius = 3.
        ball.torque = 25.
        ball.power = 9
        self.done = True

        self.sound.play()

    def render(self):
        if self.done:
            if self.size > 0:
                self.size -= .5
            else:
                return

        glPushMatrix()
        p = self.body.position
        glTranslatef(p.x, p.y, 0)
        glColor3f(0, 0, 0)
        self.draw_star()
        glPopMatrix()


trigger_types = {
    'finish': Finish,
    'coin': Coin,
    'grow': Grow,
}

class Level (object):
    gravity = (0., -8.)

    def __init__(self, levelno, init_score = 0):
        filename = 'level%s.svg' % levelno
        self.levelno = levelno
        self.layers = squirtle.load_layers(get_path(filename))
        self.render_list = []
        self.completed = False
        self.frame = 0

        self.init_b2()
        self.init_shape()
        self.init_ball(init_score)
        self.init_triggers()
        self.before_par = 5


    def init_b2(self):
        self.space = pymunk.Space(50, 50)
        self.space.gravity = pymunk.Vec2d(self.gravity[0], self.gravity[1])
        self.space.set_default_collision_handler(None, self.handle_drop, self.handle_collision, None)

    def handle_drop(self, space, arbiter):
        """"""
        #if self.ball.shape in arbiter.shapes:
        #    self.ball.drop()
        return 1

    def handle_collision(self, space, arbiter):
        for shape in arbiter.shapes:
            if hasattr(shape, 'entity'):
                shape.entity.collide(space, arbiter)

    def init_ball(self, init_score):
        x, y = self.layers['Meta'].paths_by_label['ball'][0].get_pos()
        self.ball = Ball((x, y), init_score)
        self.space.add(self.ball.body)
        self.space.add(self.ball.shape)

    def init_shape(self):
        self.shape_body = pymunk.Body(pymunk.inf, pymunk.inf)

        paths = self.layers['Shape'].paths

        for path in paths:
            self.init_shape_from_path(path)

    def init_shape_from_path(self, path):
        points = [path.transform(p) for p in path.path[0]]
        pi = 1
        while pi < len(points):
            p0 = pymunk.Vec2d(points[pi-1])
            p1 = pymunk.Vec2d(points[pi])

            d = (p1 - p0).normalized()
            p0 += d * .2
            p1 -= d * .2

            segment = pymunk.Segment(self.shape_body, p0, p1, .2)
            segment.friction = FRICTION
            segment.elasticity = .2
            segment.group = GROUP_STATIC
            self.space.add_static(segment)
            pi += 1

    def init_triggers(self):
        for cls in trigger_types.values():
            if hasattr(cls, 'compile'):
                cls.compile()

        self.triggers = []
        self.space.add_collision_handler(TYPE_BALL, TYPE_TRIGGER, Trigger.cb_begin_collide, None, None, None, self.ball)
        for path in self.layers['Meta'].paths_by_label.get('trigger', []):
            args = path.title.split(' ')
            klass = trigger_types[args[0]]
            trigger = klass(self, path, *args[1:])
            self.space.add(trigger.shape)
            self.triggers.append(trigger)
            if hasattr(trigger, 'render'):
                self.render_list.append(trigger)

    def think(self):
        self.ball.think(self.completed)
        self.space.step(1/25.)
        self.frame += 1
        self.ball.post_step()

    def get_scale_factor(self):
        radius = self.ball.shape.radius
        return 1. / (20. * radius)

    def render(self):
        glPushMatrix()

        # 0, 0 -> center of the screen.
        glTranslatef(.5, .5, 0)

        scale = self.get_scale_factor()
        glScalef(scale, scale, scale)

        p = self.ball.body.position
        glTranslatef(-p.x, -p.y, 0)

        #self.layers['Back'].draw(0, 0)
        self.layers['Shape'].draw(0, 0)

        self.ball.render()
        [trig.render() for trig in self.render_list]

        glPopMatrix()

